base_command.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. """Base Command class, and related routines"""
  2. from __future__ import absolute_import, print_function
  3. import logging
  4. import logging.config
  5. import optparse
  6. import os
  7. import platform
  8. import sys
  9. import traceback
  10. from pip._internal.cli import cmdoptions
  11. from pip._internal.cli.command_context import CommandContextMixIn
  12. from pip._internal.cli.parser import (
  13. ConfigOptionParser,
  14. UpdatingDefaultsHelpFormatter,
  15. )
  16. from pip._internal.cli.status_codes import (
  17. ERROR,
  18. PREVIOUS_BUILD_DIR_ERROR,
  19. UNKNOWN_ERROR,
  20. VIRTUALENV_NOT_FOUND,
  21. )
  22. from pip._internal.exceptions import (
  23. BadCommand,
  24. CommandError,
  25. InstallationError,
  26. NetworkConnectionError,
  27. PreviousBuildDirError,
  28. SubProcessError,
  29. UninstallationError,
  30. )
  31. from pip._internal.utils.deprecation import deprecated
  32. from pip._internal.utils.filesystem import check_path_owner
  33. from pip._internal.utils.logging import BrokenStdoutLoggingError, setup_logging
  34. from pip._internal.utils.misc import get_prog, normalize_path
  35. from pip._internal.utils.temp_dir import (
  36. global_tempdir_manager,
  37. tempdir_registry,
  38. )
  39. from pip._internal.utils.typing import MYPY_CHECK_RUNNING
  40. from pip._internal.utils.virtualenv import running_under_virtualenv
  41. if MYPY_CHECK_RUNNING:
  42. from typing import List, Optional, Tuple, Any
  43. from optparse import Values
  44. from pip._internal.utils.temp_dir import (
  45. TempDirectoryTypeRegistry as TempDirRegistry
  46. )
  47. __all__ = ['Command']
  48. logger = logging.getLogger(__name__)
  49. class Command(CommandContextMixIn):
  50. usage = None # type: str
  51. ignore_require_venv = False # type: bool
  52. def __init__(self, name, summary, isolated=False):
  53. # type: (str, str, bool) -> None
  54. super(Command, self).__init__()
  55. parser_kw = {
  56. 'usage': self.usage,
  57. 'prog': '{} {}'.format(get_prog(), name),
  58. 'formatter': UpdatingDefaultsHelpFormatter(),
  59. 'add_help_option': False,
  60. 'name': name,
  61. 'description': self.__doc__,
  62. 'isolated': isolated,
  63. }
  64. self.name = name
  65. self.summary = summary
  66. self.parser = ConfigOptionParser(**parser_kw)
  67. self.tempdir_registry = None # type: Optional[TempDirRegistry]
  68. # Commands should add options to this option group
  69. optgroup_name = '{} Options'.format(self.name.capitalize())
  70. self.cmd_opts = optparse.OptionGroup(self.parser, optgroup_name)
  71. # Add the general options
  72. gen_opts = cmdoptions.make_option_group(
  73. cmdoptions.general_group,
  74. self.parser,
  75. )
  76. self.parser.add_option_group(gen_opts)
  77. self.add_options()
  78. def add_options(self):
  79. # type: () -> None
  80. pass
  81. def handle_pip_version_check(self, options):
  82. # type: (Values) -> None
  83. """
  84. This is a no-op so that commands by default do not do the pip version
  85. check.
  86. """
  87. # Make sure we do the pip version check if the index_group options
  88. # are present.
  89. assert not hasattr(options, 'no_index')
  90. def run(self, options, args):
  91. # type: (Values, List[Any]) -> int
  92. raise NotImplementedError
  93. def parse_args(self, args):
  94. # type: (List[str]) -> Tuple[Any, Any]
  95. # factored out for testability
  96. return self.parser.parse_args(args)
  97. def main(self, args):
  98. # type: (List[str]) -> int
  99. try:
  100. with self.main_context():
  101. return self._main(args)
  102. finally:
  103. logging.shutdown()
  104. def _main(self, args):
  105. # type: (List[str]) -> int
  106. # We must initialize this before the tempdir manager, otherwise the
  107. # configuration would not be accessible by the time we clean up the
  108. # tempdir manager.
  109. self.tempdir_registry = self.enter_context(tempdir_registry())
  110. # Intentionally set as early as possible so globally-managed temporary
  111. # directories are available to the rest of the code.
  112. self.enter_context(global_tempdir_manager())
  113. options, args = self.parse_args(args)
  114. # Set verbosity so that it can be used elsewhere.
  115. self.verbosity = options.verbose - options.quiet
  116. level_number = setup_logging(
  117. verbosity=self.verbosity,
  118. no_color=options.no_color,
  119. user_log_file=options.log,
  120. )
  121. if (
  122. sys.version_info[:2] == (2, 7) and
  123. not options.no_python_version_warning
  124. ):
  125. message = (
  126. "pip 21.0 will drop support for Python 2.7 in January 2021. "
  127. "More details about Python 2 support in pip can be found at "
  128. "https://pip.pypa.io/en/latest/development/release-process/#python-2-support" # noqa
  129. )
  130. if platform.python_implementation() == "CPython":
  131. message = (
  132. "Python 2.7 reached the end of its life on January "
  133. "1st, 2020. Please upgrade your Python as Python 2.7 "
  134. "is no longer maintained. "
  135. ) + message
  136. deprecated(message, replacement=None, gone_in="21.0")
  137. if (
  138. sys.version_info[:2] == (3, 5) and
  139. not options.no_python_version_warning
  140. ):
  141. message = (
  142. "Python 3.5 reached the end of its life on September "
  143. "13th, 2020. Please upgrade your Python as Python 3.5 "
  144. "is no longer maintained. pip 21.0 will drop support "
  145. "for Python 3.5 in January 2021."
  146. )
  147. deprecated(message, replacement=None, gone_in="21.0")
  148. # TODO: Try to get these passing down from the command?
  149. # without resorting to os.environ to hold these.
  150. # This also affects isolated builds and it should.
  151. if options.no_input:
  152. os.environ['PIP_NO_INPUT'] = '1'
  153. if options.exists_action:
  154. os.environ['PIP_EXISTS_ACTION'] = ' '.join(options.exists_action)
  155. if options.require_venv and not self.ignore_require_venv:
  156. # If a venv is required check if it can really be found
  157. if not running_under_virtualenv():
  158. logger.critical(
  159. 'Could not find an activated virtualenv (required).'
  160. )
  161. sys.exit(VIRTUALENV_NOT_FOUND)
  162. if options.cache_dir:
  163. options.cache_dir = normalize_path(options.cache_dir)
  164. if not check_path_owner(options.cache_dir):
  165. logger.warning(
  166. "The directory '%s' or its parent directory is not owned "
  167. "or is not writable by the current user. The cache "
  168. "has been disabled. Check the permissions and owner of "
  169. "that directory. If executing pip with sudo, you may want "
  170. "sudo's -H flag.",
  171. options.cache_dir,
  172. )
  173. options.cache_dir = None
  174. if getattr(options, "build_dir", None):
  175. deprecated(
  176. reason=(
  177. "The -b/--build/--build-dir/--build-directory "
  178. "option is deprecated."
  179. ),
  180. replacement=(
  181. "use the TMPDIR/TEMP/TMP environment variable, "
  182. "possibly combined with --no-clean"
  183. ),
  184. gone_in="20.3",
  185. issue=8333,
  186. )
  187. if 'resolver' in options.unstable_features:
  188. logger.critical(
  189. "--unstable-feature=resolver is no longer supported, and "
  190. "has been replaced with --use-feature=2020-resolver instead."
  191. )
  192. sys.exit(ERROR)
  193. try:
  194. status = self.run(options, args)
  195. assert isinstance(status, int)
  196. return status
  197. except PreviousBuildDirError as exc:
  198. logger.critical(str(exc))
  199. logger.debug('Exception information:', exc_info=True)
  200. return PREVIOUS_BUILD_DIR_ERROR
  201. except (InstallationError, UninstallationError, BadCommand,
  202. SubProcessError, NetworkConnectionError) as exc:
  203. logger.critical(str(exc))
  204. logger.debug('Exception information:', exc_info=True)
  205. return ERROR
  206. except CommandError as exc:
  207. logger.critical('%s', exc)
  208. logger.debug('Exception information:', exc_info=True)
  209. return ERROR
  210. except BrokenStdoutLoggingError:
  211. # Bypass our logger and write any remaining messages to stderr
  212. # because stdout no longer works.
  213. print('ERROR: Pipe to stdout was broken', file=sys.stderr)
  214. if level_number <= logging.DEBUG:
  215. traceback.print_exc(file=sys.stderr)
  216. return ERROR
  217. except KeyboardInterrupt:
  218. logger.critical('Operation cancelled by user')
  219. logger.debug('Exception information:', exc_info=True)
  220. return ERROR
  221. except BaseException:
  222. logger.critical('Exception:', exc_info=True)
  223. return UNKNOWN_ERROR
  224. finally:
  225. self.handle_pip_version_check(options)